package com.example.coder_voucher.service;

import com.example.coder_voucher.entity.User;
import com.example.coder_voucher.entity.UserVoucher;
import com.example.coder_voucher.entity.Voucher;
import com.example.coder_voucher.exception.*;
import com.example.coder_voucher.repository.UserRepository;
import com.example.coder_voucher.repository.UserVoucherRepository;
import com.example.coder_voucher.repository.VoucherRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

@Service
@Transactional
public class VoucherService {
    
    @Autowired
    private VoucherRepository voucherRepository;
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private VoucherInventoryService voucherInventoryService;
    
    @Autowired
    private UserVoucherRepository userVoucherRepository;
    
    /**
     * Claim a voucher for a user using Redis-based inventory management
     */
    public UserVoucher claimVoucher(String username, String voucherCode) {
        // Find user
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> UserNotFoundException.byUsername(username));
        
        // Find voucher
        Voucher voucher = voucherRepository.findByCode(voucherCode)
            .orElseThrow(() -> VoucherNotFoundException.byCode(voucherCode));
        
        // Check basic voucher availability (status)
        if (voucher.getStatus() != Voucher.VoucherStatus.ACTIVE) {
            throw VoucherNotAvailableException.inactive(voucherCode);
        }
        
        // Initialize/sync Redis inventory if needed
        voucherInventoryService.syncInventoryWithDatabase(voucher);
        
        // Try to claim voucher using Redis Lua script
        VoucherInventoryService.VoucherClaimResult claimResult = 
            voucherInventoryService.claimVoucher(user.getId(), voucher);
        
        if (!claimResult.isSuccess()) {
            throw new VoucherNotAvailableException(claimResult.getMessage());
        }
        
        // Create user voucher record in database
        UserVoucher userVoucher = new UserVoucher(user, voucher);
        UserVoucher savedUserVoucher = userVoucherRepository.save(userVoucher);
        
        // Update voucher claimed quantity in database (eventually consistent)
        voucher.setClaimedQuantity(voucher.getClaimedQuantity() + 1);
        voucherRepository.save(voucher);
        
        return savedUserVoucher;
    }
    
    /**
     * Rollback a claimed voucher using Redis-based inventory management
     */
    public UserVoucher rollbackVoucher(String username, String voucherCode) {
        // Find user
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> UserNotFoundException.byUsername(username));
        
        // Find voucher
        Voucher voucher = voucherRepository.findByCode(voucherCode)
            .orElseThrow(() -> VoucherNotFoundException.byCode(voucherCode));
        
        // Find the user's voucher claim in database
        UserVoucher userVoucher = userVoucherRepository.findByUserAndVoucher(user, voucher)
            .orElseThrow(() -> VoucherCannotBeRollbackException.notClaimed(voucherCode));
        
        // Check if voucher can be rolled back (database validation)
        validateVoucherRollback(userVoucher, voucherCode);
        
        // Try to rollback voucher using Redis Lua script
        VoucherInventoryService.VoucherRollbackResult rollbackResult = 
            voucherInventoryService.rollbackVoucher(user.getId(), voucher);
        
        if (!rollbackResult.isSuccess()) {
            throw new VoucherCannotBeRollbackException(rollbackResult.getMessage());
        }
        
        // Update user voucher record in database
        userVoucher.rollback();
        UserVoucher savedUserVoucher = userVoucherRepository.save(userVoucher);
        
        // Update voucher claimed quantity in database (eventually consistent)
        voucher.setClaimedQuantity(Math.max(0, voucher.getClaimedQuantity() - 1));
        voucherRepository.save(voucher);
        
        return savedUserVoucher;
    }
    
    /**
     * Get all available vouchers
     */
    @Transactional(readOnly = true)
    public List<Voucher> getAvailableVouchers() {
        return voucherRepository.findAvailableVouchers(LocalDateTime.now());
    }
    
    /**
     * Get user's claimed vouchers
     */
    @Transactional(readOnly = true)
    public List<UserVoucher> getUserVouchers(String username) {
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> UserNotFoundException.byUsername(username));
        
        return userVoucherRepository.findByUserOrderByClaimedAtDesc(user);
    }
    
    /**
     * Get user's rollbackable vouchers
     */
    @Transactional(readOnly = true)
    public List<UserVoucher> getRollbackableVouchers(String username) {
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> UserNotFoundException.byUsername(username));
        
        return userVoucherRepository.findRollbackableVouchersByUser(user);
    }
    
    /**
     * Get voucher by code
     */
    @Transactional(readOnly = true)
    public Voucher getVoucherByCode(String voucherCode) {
        return voucherRepository.findByCode(voucherCode)
            .orElseThrow(() -> VoucherNotFoundException.byCode(voucherCode));
    }
    
    /**
     * Create a new voucher and initialize Redis inventory
     */
    public Voucher createVoucher(Voucher voucher) {
        // Check if voucher code already exists
        if (voucherRepository.existsByCode(voucher.getCode())) {
            throw new IllegalArgumentException("Voucher code already exists: " + voucher.getCode());
        }
        
        // Save voucher to database first
        Voucher savedVoucher = voucherRepository.save(voucher);
        
        // Initialize Redis inventory
        voucherInventoryService.initVoucherInventory(savedVoucher);
        
        return savedVoucher;
    }
    
    /**
     * Get all vouchers
     */
    @Transactional(readOnly = true)
    public List<Voucher> getAllVouchers() {
        return voucherRepository.findAll();
    }
    
    /**
     * Mark voucher as used
     */
    public void markVoucherAsUsed(String username, String voucherCode) {
        // Find user
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> UserNotFoundException.byUsername(username));
        
        // Find voucher
        Voucher voucher = voucherRepository.findByCode(voucherCode)
            .orElseThrow(() -> VoucherNotFoundException.byCode(voucherCode));
        
        // Find the user's voucher claim
        UserVoucher userVoucher = userVoucherRepository.findByUserAndVoucher(user, voucher)
            .orElseThrow(() -> new IllegalArgumentException("User has not claimed this voucher"));
        
        // Mark as used in database
        userVoucher.markAsUsed();
        userVoucherRepository.save(userVoucher);
        
        // Mark as used in Redis
        voucherInventoryService.markVoucherAsUsed(user.getId(), voucherCode);
    }
    
    /**
     * Get current inventory from Redis
     */
    @Transactional(readOnly = true)
    public Long getCurrentInventory(Long voucherId) {
        return voucherInventoryService.getCurrentInventory(voucherId);
    }
    
    /**
     * Validate voucher availability for claiming
     */
    private void validateVoucherAvailability(Voucher voucher) {
        LocalDateTime now = LocalDateTime.now();
        
        // Check if voucher is active
        if (voucher.getStatus() != Voucher.VoucherStatus.ACTIVE) {
            throw VoucherNotAvailableException.inactive(voucher.getCode());
        }
        
        // Check if voucher period has started
        if (now.isBefore(voucher.getStartTime())) {
            throw VoucherNotAvailableException.notStarted(voucher.getCode());
        }
        
        // Check if voucher has expired
        if (now.isAfter(voucher.getEndTime())) {
            throw VoucherNotAvailableException.expired(voucher.getCode());
        }
        
        // Check if voucher is sold out
        if (voucher.getClaimedQuantity() >= voucher.getTotalQuantity()) {
            throw VoucherNotAvailableException.soldOut(voucher.getCode());
        }
    }
    
    /**
     * Validate if voucher can be rolled back
     */
    private void validateVoucherRollback(UserVoucher userVoucher, String voucherCode) {
        switch (userVoucher.getStatus()) {
            case USED:
                throw VoucherCannotBeRollbackException.alreadyUsed(voucherCode);
            case ROLLBACK:
                throw VoucherCannotBeRollbackException.alreadyRolledBack(voucherCode);
            case EXPIRED:
                throw VoucherCannotBeRollbackException.notClaimed(voucherCode);
            case CLAIMED:
                // Check if voucher has been used
                if (userVoucher.getUsedAt() != null) {
                    throw VoucherCannotBeRollbackException.alreadyUsed(voucherCode);
                }
                break;
        }
    }
}